# uncompyle6 version 3.2.3
# Python bytecode 3.6 (3379)
# Decompiled from: Python 3.6.8 |Anaconda custom (64-bit)| (default, Feb 21 2019, 18:30:04) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: helpers\updater\__init__.py
import logging, time, requests, socket, os.path
from threading import Lock
from sqlalchemy import select as sql_select
from sqlalchemy import func as sql_func
from helpers.licensing import get_local_license
from db import Connection
from db.tables.scan_session_jobs import ScanSessionJobRow
from db.tables.system_config import SystemConfigRow
from helpers.licensing.features import Features, BaseLicense

logger = logging.getLogger("helpers.updater")


class Updater:
    UPDATE_CONFIRMED_MARKER = "update_confirmed"

    def __init__(
        self,
        current_version,
        updates_storage,
        install_dir=".",
        check_interval=7200,
        updates_server=None,
        shard="master",
        proxies_cb=None,
        ssl_ca_bundle_path=True,
    ):
        if updates_server is None:
            updates_server = "https://updates.acunetix.com/"
        self.current_version = current_version
        self.check_interval = check_interval
        self.should_stop = False
        self.updates_server = updates_server
        self.install_dir = install_dir
        self.shard = shard
        self.updates_storage = updates_storage
        self.proxies_cb = proxies_cb
        self.update_info = None
        self.ssl_ca_bundle_path = ssl_ca_bundle_path
        self._Updater__running = False
        self._Updater__update_status = "none"
        self._Updater__lock = Lock()

    def stop(self):
        self.should_stop = True

    @property
    def running(self):
        return self._Updater__running

    @staticmethod
    def __get_private_ip():
        """
        dirty method to get the local ip of the machine
        :return:
        """
        s = None
        try:
            try:
                s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                s.connect(("172.16.0.0", 0))
                return s.getsockname()[0]
            except Exception as e:
                logger.error("__get_private_ip: %s", e)

        finally:
            try:
                s.close()
            except Exception as e:
                logger.debug("s.close %s", e)

        return "-"

    def __sleep(self, t):
        for i in range(0, t):
            time.sleep(1)
            if self.should_stop:
                break

    @property
    def update_preference(self):
        """
        updates: auto, notify, disabled
        :return:
        """
        with Connection(self.shard) as (db):
            q = sql_select((SystemConfigRow,)).where(SystemConfigRow.name == "updates")
            entry = db.execute(q).fetchone()
            if not entry:
                return "notify"
            return entry.value

    def confirm_that_update_is_permitted(self):
        if self.update_status == "waiting_for_user":
            self.update_status = "waiting_for_scans"
            return True
        else:
            return False

    @property
    def update_status(self):
        return self._Updater__update_status

    @update_status.setter
    def update_status(self, value):
        """
        :param value: none, waiting_for_user, waiting_for_scans, updating
        :return:
        """
        if value == "waiting_for_scans":
            open(
                os.path.join(self.updates_storage, self.UPDATE_CONFIRMED_MARKER), "wt"
            ).write("True")
        self._Updater__update_status = value

    def remove_update_marker(self):
        try:
            os.remove(os.path.join(self.updates_storage, self.UPDATE_CONFIRMED_MARKER))
        except Exception as e:
            logger.debug("remove_update_marker %s", e)

    def first_run(self):
        """
        :return: returns True if update was found and prepared for install
        """
        l = BaseLicense.get_system_license()
        if not l.has_feature(Features.ON_PREMISE_UPDATES):
            return False
        if not os.path.exists(
            os.path.join(self.updates_storage, self.UPDATE_CONFIRMED_MARKER)
        ):
            return False
        logger.info("update confirmation marker found")
        try:
            try:
                if self.update_preference == "disabled":
                    logger.info("update marker found but updates where disabled")
                    return False
                return self.update_the_system()
            except Exception as e:
                logger.exception("first_run failed with %s", e)
                return False

        finally:
            self.remove_update_marker()

    def run(self):
        """
        :return:
        """
        self._Updater__running = True
        logger.info("update thread started")
        while not self.should_stop:
            try:
                l = BaseLicense.get_system_license()
                if not l.has_feature(Features.ON_PREMISE_UPDATES):
                    self.update_info = None
                    self.update_status = "none"
                    self._Updater__sleep(600)
                    continue
                update_preference = self.update_preference
                if update_preference == "disabled":
                    self._Updater__sleep(60)
                    self.update_info = None
                    self.update_status = "none"
                    continue
                if update_preference == "auto":
                    if self.update_status == "waiting_for_user":
                        self.update_status = "waiting_for_scans"
                if self.update_status == "none":
                    updates_data = False
                    try:
                        updates_data = self.check_for_updates()
                    except Exception as e:
                        logger.error("check_for_updates failed with %s", e)

                    if not updates_data:
                        self._Updater__sleep(self.check_interval)
                        continue
                    self.update_info = updates_data
                    if update_preference == "auto":
                        self.update_status = "waiting_for_scans"
                    else:
                        self.update_status = "waiting_for_user"
                else:
                    if self.update_status == "waiting_for_user":
                        self._Updater__sleep(10)
                    else:
                        if self.update_status == "waiting_for_scans":
                            self._Updater__sleep(10)
                            q = sql_select(
                                (sql_func.count(ScanSessionJobRow.scan_session_id),)
                            ).where(
                                ScanSessionJobRow.status.in_(
                                    ("starting", "processing", "aborting")
                                )
                            )
                            with Connection("master") as (db):
                                scan_count = db.execute(q).scalar()
                            if scan_count == 0:
                                if self.update_the_system():
                                    break
                                else:
                                    self._Updater__sleep(600)
                            else:
                                self._Updater__sleep(60)
                        else:
                            self._Updater__sleep(600)
            except Exception as e:
                logger.exception("updater main loop failed with %s", e)
                self._Updater__sleep(60)

        logger.info("update thread exited")
        self._Updater__running = False

    def check_for_updates(self, download_update=False):
        license_info = get_local_license()
        if license_info.get("maintenance_expired") or license_info.get("expired"):
            return False
        request_data = {
            k: v
            for k, v in license_info.items()
            if k
            in frozenset(
                {"phone", "email", "license_key", "country", "company", "name"}
            )
        }
        request_data["major_version"] = self.current_version["major_version"]
        request_data["minor_version"] = self.current_version["minor_version"]
        request_data["build_number"] = self.current_version["build_number"]
        request_data.update(
            dict(
                custom_data="-",
                private_ip=self._Updater__get_private_ip(),
                install_dir=self.install_dir,
                is_application_request=True,
                check_only=download_update is False,
            )
        )
        result = requests.post(
            self.updates_server + "v12",
            data=request_data,
            proxies=self.proxies_cb() if self.proxies_cb else None,
            verify=self.ssl_ca_bundle_path,
        )
        if result.headers.get("Content-Type") != "application/json":
            raise Exception(
                "abnormal response [%s]: %s", result.status_code, result.text
            )
        response = result.json()
        logger.info("update request returned %s", response)
        if result.status_code != 200:
            return False
        if not response["update"]:
            return False
        if not download_update:
            return response
        update_file_name = "%s.%s.%s.exe" % (
            response["major_version"],
            response["minor_version"],
            response["build_number"],
        )
        if os.path.exists(os.path.join(self.updates_storage, update_file_name)):
            logger.error(
                "update file %s already exists, aborting the process",
                os.path.join(self.updates_storage, update_file_name),
            )
            return response
        temp_update_path = (
            os.path.join(self.updates_storage, update_file_name) + ".temp"
        )
        try:
            try:
                with open(temp_update_path, "wb") as (fh):
                    dw_response = requests.get(
                        response["link"],
                        stream=True,
                        proxies=self.proxies_cb() if self.proxies_cb else None,
                        verify=self.ssl_ca_bundle_path,
                    )
                    if not dw_response.ok:
                        raise Exception("download failed")
                    else:
                        for block in dw_response.iter_content(1024):
                            fh.write(block)

                os.rename(
                    temp_update_path,
                    os.path.join(self.updates_storage, update_file_name),
                )
                return response
            except Exception as e:
                logger.error("download failed %s", e)
                return False

        finally:
            try:
                os.unlink(temp_update_path)
            except Exception as e:
                logger.debug("unlink temp_update_path %s", e)

    def update_the_system(self):
        logger.info("update_the_system start ...")
        try:
            self.update_status = "updating"
            update_info = self.check_for_updates(download_update=True)
            if update_info is False:
                logger.info("update aborted")
                self.update_info = None
                self.update_status = "none"
                return False
            logger.info("restarting system for updating")
            self.update_status = "restarting"
            self.remove_update_marker()
            self.stop()
            return True
        except Exception as e:
            logger.error("update_the_system failed with %s", e)
            self.update_info = None
            self.update_status = "none"
            return False
